﻿using System;
using System.ComponentModel;

using Supremacy.Entities;
using Supremacy.Scripting.Utility;
using Supremacy.Utility;

namespace Supremacy.Game
{
    [Serializable]
    [ImmutableObject(true)]
    public struct SectorClaim : IEquatable<SectorClaim>, IFormattable
    {
        private const uint OwnerIdMask = 0xFFFF0000;
        private const byte OwnerIdOffset = 16;
        private const uint WeightMask = 0x0000FFFF;

        private readonly uint _data;

        private SectorClaim(uint data)
        {
            _data = data;
        }

        public SectorClaim(Civilization owner, int weight)
            : this(Guard.ArgumentNotNull(owner, "owner").CivID, weight) {}

        public SectorClaim(GameObjectID ownerId, int weight)
        {
            if (!ownerId.IsValid)
                throw new ArgumentException("Invalid Civilization ID.", "ownerId");

            _data = GetClaim(ownerId, weight);
        }

        public GameObjectID OwnerID
        {
            get { return ExtractOwnerID(_data); }
        }

        public Civilization Owner
        {
            get { return ExtractOwner(_data); }
        }

        public int Weight
        {
            get { return ExtractWeight(_data); }
        }

        #region Helper Methods

        private static uint GetClaim(int civId, int weight)
        {
            return (uint)(((civId << OwnerIdOffset) & OwnerIdMask) | (weight & WeightMask));
        }

        private static GameObjectID ExtractOwnerID(uint claim)
        {
            return (int)((claim & OwnerIdMask) >> OwnerIdOffset);
        }

        private static Civilization ExtractOwner(uint claim)
        {
            var ownerId = ExtractOwnerID(claim);

            if (!ownerId.IsValid)
                return null;

            Civilization owner;
            
            if (GameContext.Current.Civilizations.TryGetValue(ownerId, out owner))
                return owner;

            return null;
        }

        private static int ExtractWeight(uint claim)
        {
            return (int)(claim & WeightMask);
        }

        #endregion

        #region Conversion Operators

        public static implicit operator uint(SectorClaim claim)
        {
            return claim._data;
        }

        public static implicit operator SectorClaim(uint claimData)
        {
            return new SectorClaim(claimData);
        }

        #endregion

        #region Equality Members

        public bool Equals(SectorClaim other)
        {
            return other._data == _data;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;

            var type = obj.GetType();
            if (type != typeof(SectorClaim) &&
                !TypeUtils.IsImplicitNumericConversion(type, typeof(uint)))
            {
                return false;
            }

            return Equals((uint)obj);
        }

        public override int GetHashCode()
        {
            return this.OwnerID;
        }

        public static bool operator ==(SectorClaim left, SectorClaim right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(SectorClaim left, SectorClaim right)
        {
            return !left.Equals(right);
        }

        #endregion

        #region Implementation of IFormattable

        string IFormattable.ToString(string format, IFormatProvider formatProvider)
        {
            return string.Format(
                formatProvider,
                format,
                _data);
        }

        #endregion
    }
}